﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Slam.SharePoint;

namespace Slam.Configuration
{
	/// <summary>
	/// The XmlConfigurationManager provides access to an XML file represetnation of a SLAM configuration.
	/// XmlConfigurationManager is the default implementation of IConfigurationManager.  Its DataMapping
	/// is an XmlSqlDataMapping and it also registers an XmlConnectionStringsSection as a default source
	/// of connection strings.
	/// </summary>
	/// <remarks>
	/// The XmlConfigurationManager class loads a SLAM configuration from an XML file that follows an expected schema.
	/// The SLAM configuration is bounded by <![CDATA[<Configuration>]]> tags.  The DataMapping is bounded by <![CDATA[<DataMapping>]]> tags and consists of nested <![CDATA[<List>]]> and <![CDATA[<ContentType>]]> tags.
	/// These <![CDATA[<List>]]> and <![CDATA[<ContentType>]]> tags are considered "configured types".  Configured types consist of a Field collection.  The following is an example
	/// XML SLAM configuration with basic DataMapping.
	/// </remarks>
	public class XmlConfigurationManager : IConfigurationManager
	{
		#region Private State

		/// <summary>
		/// Configuration file XDocument
		/// </summary>
		private XDocument _configXml;

		/// <summary>
		/// Data Mappings
		/// </summary>
		private Dictionary<string, DataMapping> _dataMappings;

		/// <summary>
		/// Dictionary of registered section delegates.
		/// </summary>
		private Dictionary<string, Func<object, object>> _sectionDelegates;

		#endregion

		#region Properties

		/// <summary>
		/// Gets XML configuration's DataMapping section based on the current SLAM Item Context.
		/// </summary>
		public DataMapping DataMapping
		{
			get
			{   
				DataMapping currentDataMapping = _dataMappings["DEFAULTMAPPING"];
				if (SlamItemContext.ListItem != null)
				{
					string siteCollection = SlamItemContext.ListItem.Web.Site.ServerRelativeUrl.TrimStart('/');
					if (!String.IsNullOrEmpty(siteCollection) && _dataMappings.ContainsKey(siteCollection))
					{
						currentDataMapping = _dataMappings[siteCollection];
					}
				}
				return currentDataMapping;
			}
		}

		/// <summary>
		/// All of XML configuration's DataMapping sections.
		/// </summary>
		public Dictionary<string, DataMapping> DataMappings
		{
			get
			{
				return _dataMappings;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Creates a new XmlConfigurationManager instance, where the loaded configuration file is assumed
		/// to be "slam.xml" in the BaseDirectory of the current AppDomain.
		/// 
		/// The "ConnectionStrings" and "TypeIdDefinitions" configuration sections are then registered as 
		/// XmlConnectionStringSection and XmlTypeIdDefinitionSection, respectively.
		/// </summary>
		public XmlConfigurationManager()
		{
			string config = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\'), "slam.config");            
			_configXml = XDocument.Load(config);

			LoadDataMappings();

			_sectionDelegates = new Dictionary<string, Func<object, object>>();

			RegisterSection("ConnectionStrings",
				configInput =>
				{
					object section = null;
					if (configInput is XDocument)
					{
						section = new XmlConnectionStringSection((XDocument)configInput);
					}
					return section;
				});

			RegisterSection("TypeIdDefinitions",
				configInput =>
				{
					object section = null;
					if (configInput is XDocument)
					{
						section = new XmlTypeIdDefinitionSection((XDocument)configInput);
					}
					return section;
				});
		}

		/// <summary>
		/// Creates a new XmlConfigurationManager instance.  Sets the configuration XML file location to the
		/// given configurationLocation.  If configurationLocation is null or empty, the configuration file is assumed
		/// to be "slam.xml" in the BaseDirectory of the current AppDomain.
		/// 
		/// The "ConnectionStrings" and "TypeIdDefinitions" configuration sections are then registered as 
		/// XmlConnectionStringSection and XmlTypeIdDefinitionSection, respectively.
		/// </summary>
		/// <param name="configurationLocation"></param>
		public XmlConfigurationManager(string configurationLocation)
		{
			string config = "";
			if (String.IsNullOrEmpty(configurationLocation))
			{
				config = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\'), "slam.config");
			}
			else
			{
				config = configurationLocation;
			}

			_configXml = XDocument.Load(config);
			LoadDataMappings();

			_sectionDelegates = new Dictionary<string, Func<object, object>>();

			RegisterSection("ConnectionStrings",
				configInput =>
				{
					object section = null;
					if (configInput is XDocument)
					{
						section = new XmlConnectionStringSection((XDocument)configInput);
					}
					return section;
				});

			RegisterSection("TypeIdDefinitions",
				configInput =>
				{
					object section = null;
					if (configInput is XDocument)
					{
						section = new XmlTypeIdDefinitionSection((XDocument)configInput);
					}
					return section;
				});
		}

		#endregion

		

		#region IConfigurationManager Members

		/// <summary>
		/// Returns a registered typed custom configuration section by name.
		/// </summary>
		/// <param name="sectionName">Name of the section as registered</param>
		/// <returns></returns>
		public object GetSection(string sectionName)
		{
			object section = null;
			if (_sectionDelegates.ContainsKey(sectionName))
			{
				section = _sectionDelegates[sectionName].Invoke(_configXml);
			}
			else
			{
				throw new ConfigurationSectionNotFoundException(String.Format("SLAM Configuration \"{0}\" Section cannot be found.", sectionName));
			}
			
			return section;
		}

		/// <summary>
		/// Returns a registered typed custom configuration section by name.
		/// </summary>
		/// <typeparam name="SECTIONTYPE">Type of section expected</typeparam>
		/// <param name="sectionName">Name of the section as registered</param>
		/// <returns>Strongly typed object representing a section</returns>
		public SECTIONTYPE GetSection<SECTIONTYPE>(string sectionName)
		{
			object section = null;
			if (_sectionDelegates.ContainsKey(sectionName))
			{
				section = _sectionDelegates[sectionName].Invoke(_configXml);
			}
			else
			{
				throw new ConfigurationSectionNotFoundException(String.Format("SLAM Configuration \"{0}\" Section cannot be found.", sectionName));
			}
			
			return (SECTIONTYPE)section;
		}

		/// <summary>
		/// Registers a Configuration section with a Name and a delegate to be executed upon a GetSection call for
		/// that section Name.
		/// </summary>
		/// <param name="sectionName">Name of the section to register</param>
		/// <param name="getSection">Delegate to be executed by GetSection</param>
		public void RegisterSection(string sectionName, Func<object, object> getSection)
		{
			if (!_sectionDelegates.ContainsKey(sectionName))
			{
				_sectionDelegates.Add(sectionName, getSection);
			}
		}

		/// <summary>
		/// Registers a Configuration section with a Name and a delegate to be executed upon a GetSection call for
		/// that section Name.
		/// </summary>
		/// <param name="sectionName">Name of the section to register</param>
		/// <param name="getSection">Delegate to be executed by GetSection</param>
		/// <param name="replace">Indicates whether to overwrite getSection delegate for the section with the given Name if previously registered</param>
		public void RegisterSection(string sectionName, Func<object, object> getSection, bool replace)
		{
			if (!_sectionDelegates.ContainsKey(sectionName))
			{
				_sectionDelegates.Add(sectionName, getSection);
			}
			else if (replace)
			{
				_sectionDelegates[sectionName] = getSection;
			}
		}

		#endregion

		#region Private Members

		public void LoadDataMappings()
		{
			if (_dataMappings == null)
			{
				_dataMappings = new Dictionary<string, DataMapping>();
			}

			if (_configXml.Root.Elements().Where(node => node.Name == "DataMapping").Count() > 1)
			{
				bool containsRoot = _configXml.Root.Elements().Where(
										node => node.Name == "DataMapping" && 
												(node.Attribute("SiteCollection") == null || 
												String.IsNullOrEmpty(node.Attribute("SiteCollection").Value))).Count() > 0;

				foreach (XElement node in _configXml.Root.Elements().Where(node => node.Name == "DataMapping"))
				{
					if (containsRoot && (node.Attribute("SiteCollection") == null || String.IsNullOrEmpty(node.Attribute("SiteCollection").Value)) && !_dataMappings.ContainsKey("DEFAULTMAPPING"))
					{
						//Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
						_dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
					}
					else if (!containsRoot && !_dataMappings.ContainsKey("DEFAULTMAPPING"))
					{
						//Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
						_dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
					}
					else if(node.Attribute("SiteCollection") != null)
					{
						_dataMappings.Add(node.Attribute("SiteCollection").Value, new XmlSqlDataMapping(_configXml, node.Attribute("SiteCollection").Value));
					}
				}
			}
			else if (_configXml.Root.Elements().Where(node => node.Name == "DataMapping").Count() > 0)
			{
				//Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
				_dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
			}
			else
			{
				throw new ConfigurationErrorsException("SLAM XML Configuration does not contain any DataMapping sections.  The SLAM XML Configuraiton must contain at least one DataMapping section.");
			}
		}

		#endregion
	}
}
